package easier.paging3;

import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelKt;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner;
import androidx.paging.CombinedLoadStates;
import androidx.paging.LoadState;
import androidx.paging.Pager;
import androidx.paging.PagingConfig;
import androidx.paging.PagingData;
import androidx.paging.PagingDataAdapter;
import androidx.paging.PagingSource;
import androidx.paging.PagingSource.LoadResult.Error;
import androidx.paging.PagingSource.LoadResult.Page;
import androidx.paging.PagingState;
import androidx.paging.rxjava3.PagingRx;
import androidx.paging.rxjava3.RxPagingSource;
import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import androidx.recyclerview.widget.RecyclerView;
import easier.paging3.Paging3Adapter.ViewHolder;
import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.core.SingleSource;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.functions.Consumer;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.schedulers.Schedulers;
import java.util.List;
import java.util.Objects;
import kotlin.jvm.functions.Function0;
import kotlinx.coroutines.CoroutineScope;

/**
 * 简化paging3使用
 * 使用通用流程创建一个PagingDataAdapter
 *
 * @param <T> 数据类型
 */
public abstract class Paging3Adapter< T > extends PagingDataAdapter< T, ViewHolder > implements DefaultLifecycleObserver {

    private final Paging3Model< T > mViewModel;

    private Lifecycle mLifecycle;
    private Disposable mDisposable;

    @SuppressWarnings( "unchecked" )
    public Paging3Adapter( ViewModelStoreOwner owner, ItemCallback< T > itemCallback ) {

        super( itemCallback );

        mViewModel = new ViewModelProvider( owner ).get( Paging3Model.class );
        mViewModel.mPaging3AdapterFactory = this;
    }

    @Override
    public void onDestroy( @NonNull LifecycleOwner owner ) {

        if ( mDisposable != null && !mDisposable.isDisposed() ) {
            mDisposable.dispose();
            mDisposable = null;
        }
        if ( mLifecycle != null ) {
            mLifecycle.removeObserver( this );
        }
    }

    /**
     * @return 创建数据流
     */
    private Source createSource() {

        return new Source();
    }

    /**
     * 通用代码
     * {@link CombinedLoadStates}异常时是否需要展示网络异常
     *
     * @param error 异常{@link LoadState.Error}
     */
    protected void notifyError( Throwable error ) {

    }

    /**
     * 连接数据到adapter,然后展示数据
     *
     * @param lifecycle 生命周期
     */
    public void submitData( Lifecycle lifecycle ) {

        if ( mDisposable != null && !mDisposable.isDisposed() ) {
            mDisposable.dispose();
            mDisposable = null;
        }

        if ( mLifecycle != null ) {
            mLifecycle.removeObserver( this );
        }

        mLifecycle = lifecycle;
        mLifecycle.addObserver( this );

        mDisposable = mViewModel.flowable().subscribe(
            new Consumer< PagingData< T > >() {
                @Override
                public void accept( PagingData< T > tPagingData ) throws Throwable {

                    submitData( lifecycle, tPagingData );
                }
            },
            new Consumer< Throwable >() {
                @Override
                public void accept( Throwable throwable ) throws Throwable {

                    notifyError( throwable );
                }
            }
        );
    }

    /**
     * 创建数据流
     */
    public static class Paging3Model< T > extends ViewModel {

        /**
         * 链接{@link Paging3Model}和{@link Paging3Adapter}
         */
        private Paging3Adapter< T > mPaging3AdapterFactory;

        /**
         * @return 创建数据流
         */
        public Flowable< PagingData< T > > flowable() {

            CoroutineScope viewModelScope = ViewModelKt.getViewModelScope( this );

            Pager< Integer, T > pager = new Pager<>(
                new PagingConfig( mPaging3AdapterFactory.getPageSize() ),
                null,
                new Function0< PagingSource< Integer, T > >() {
                    @Override
                    public PagingSource< Integer, T > invoke() {

                        return mPaging3AdapterFactory.createSource();
                    }
                } );

            Flowable< PagingData< T > > flowable = PagingRx.getFlowable( pager );
            return PagingRx.cachedIn( mPaging3AdapterFactory.transform( flowable ), viewModelScope );
        }
    }

    /**
     * 变换数据
     *
     * @param dataFlowable 此处变换数据
     *
     * @return 变换后数据
     */
    protected Flowable< PagingData< T > > transform(
        Flowable< PagingData< T > > dataFlowable ) {

        return dataFlowable;
    }

    /**
     * 根据key获取数据
     */
    public class Source extends RxPagingSource< Integer, T > {

        @Nullable
        @Override
        public Integer getRefreshKey( @NonNull PagingState< Integer, T > state ) {

            Integer anchorPosition = state.getAnchorPosition();
            if ( anchorPosition == null ) {
                return null;
            }

            Page< Integer, T > anchorPage = state.closestPageToPosition( anchorPosition );
            if ( anchorPage == null ) {
                return null;
            }

            Integer prevKey = anchorPage.getPrevKey();
            if ( prevKey != null ) {
                return prevKey + 1;
            }

            Integer nextKey = anchorPage.getNextKey();
            if ( nextKey != null ) {
                return nextKey - 1;
            }

            return null;
        }

        @NonNull
        @Override
        public Single< LoadResult< Integer, T > > loadSingle( @NonNull LoadParams< Integer > params ) {

            // Start refresh at page 1 if undefined.
            Integer requestPage = params.getKey();
            if ( requestPage == null ) {
                requestPage = 1;
            }
            final int i = requestPage;
            int next = requestPage + 1;

            return Single.just( "" )
                .subscribeOn( Schedulers.io() )
                .flatMap( new Function< String, SingleSource< List< T > > >() {
                    @Override
                    public SingleSource< List< T > > apply( String s ) throws Throwable {

                        return loadData( i, getPageSize() );
                    }
                } )
                .map( new Function< List< T >, LoadResult< Integer, T > >() {
                    @Override
                    public LoadResult< Integer, T > apply( List< T > data ) throws Throwable {

                        return new Page< Integer, T >(
                            data,
                            i > 1 ? i - 1 : null,
                            (data.size() == 0 || data.size() < getPageSize()) ? null : next,
                            Page.COUNT_UNDEFINED,
                            Page.COUNT_UNDEFINED
                        );
                    }
                } )
                .onErrorReturn( new Function< Throwable, LoadResult< Integer, T > >() {
                    @Override
                    public LoadResult< Integer, T > apply( Throwable throwable ) throws Throwable {

                        return new Error<>( throwable );
                    }
                } );
        }
    }

    /**
     * 获取该页数据
     *
     * @param page     页数
     * @param pageSize 每页数据量
     *
     * @return 数据
     */
    protected abstract SingleSource< List< T > > loadData( int page, int pageSize );

    /**
     * @return 配置每页数据量
     */
    protected int getPageSize() {

        return 15;
    }

    /**
     * 辅助类
     */
    public static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder( @NonNull View view ) {

            super( view );
        }
    }

    public static class SimpleDiffCallback< T > extends ItemCallback< T > {

        @Override
        public boolean areItemsTheSame( @NonNull T oldItem, @NonNull T newItem ) {

            return Objects.equals( oldItem, newItem );
        }

        @Override
        public boolean areContentsTheSame( @NonNull T oldItem, @NonNull T newItem ) {

            return true;
        }

        @Nullable
        @Override
        public Object getChangePayload( @NonNull T oldItem, @NonNull T newItem ) {

            return "";
        }
    }
}
